package com.mingyuan.library.infra.lending.patron.convert;

import com.mingyuan.library.domain.lending.book.model.valueobject.BookId;
import com.mingyuan.library.domain.lending.librarybranch.model.valueobject.LibraryBranchId;
import com.mingyuan.library.domain.lending.patron.model.aggregate.Patron;
import com.mingyuan.library.domain.lending.patron.model.valueobject.Hold;
import com.mingyuan.library.domain.lending.patron.model.valueobject.OverdueCheckouts;
import com.mingyuan.library.domain.lending.patron.model.valueobject.PatronHolds;
import com.mingyuan.library.domain.lending.patron.model.valueobject.PatronInformation;
import com.mingyuan.library.domain.lending.patron.strategy.PlacingOnHoldPolicy;
import com.mingyuan.library.infra.lending.patron.convert.named.CustomTypeConverter;
import com.mingyuan.library.infra.lending.patron.entity.OverdueCheckoutDatabaseEntity;
import com.mingyuan.library.infra.lending.patron.entity.PatronDatabaseEntity;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static java.util.stream.Collectors.*;

/**
 * @author yangzk
 * @version 1.0.0
 * @date 2024.01.16 15:58
 */
@Mapper(uses = {CustomTypeConverter.class})
public interface PatronInfraConvert {
    /**
     * 映射器
     */
    PatronInfraConvert INSTANCE = Mappers.getMapper(PatronInfraConvert.class);
    /**
     * po 转换 domain model
     * @author yangzk
     * @param entity 读者数据实体信息
     * @date 2024/1/16 15:59
     **/
    @Mappings({
            @Mapping(source = "patronId",target = "patronId", qualifiedByName = "toPatronId"),
            @Mapping(source = "patronType",target = "type")
    })
    PatronInformation mapping(PatronDatabaseEntity entity);

    /**
     * 读者数据实体转换领域实体
     * @author yangzk
     * @param entity 读者数据实体信息
     * @date 2024/1/16 16:06
     **/
    default Patron map(PatronDatabaseEntity entity) {
        PatronInfraConvert patronInfraConvert = PatronInfraConvert.INSTANCE;
        PatronInformation patronInformation = patronInfraConvert.mapping(entity);
        Map<LibraryBranchId, Set<BookId>> overdueCheckouts = patronInfraConvert.mapPatronOverdueCheckouts(entity);
        Set<Tuple2<BookId, LibraryBranchId>> patronHolds = patronInfraConvert.mapPatronHolds(entity);
        return new Patron(patronInformation,
                PlacingOnHoldPolicy.allCurrentPolicies(),
                new OverdueCheckouts(overdueCheckouts),
                new PatronHolds(
                        patronHolds
                                .stream()
                                .map(tuple -> new Hold(tuple._1, tuple._2))
                                .collect(toSet())));
    }

    /**
     * 映射读者逾期借阅信息
     * @author yangzk
     * @param patronDatabaseEntity 读者数据实体信息
     * @date 2024/1/16 16:06
     **/
    default Map<LibraryBranchId, Set<BookId>> mapPatronOverdueCheckouts(PatronDatabaseEntity patronDatabaseEntity) {
        return
                patronDatabaseEntity
                        .checkouts
                        .stream()
                        .collect(groupingBy(OverdueCheckoutDatabaseEntity::getLibraryBranchId, toSet()))
                        .entrySet()
                        .stream()
                        .collect(toMap(
                                (Map.Entry<UUID, Set<OverdueCheckoutDatabaseEntity>> entry) -> new LibraryBranchId(entry.getKey()), entry -> entry
                                        .getValue()
                                        .stream()
                                        .map(entity -> (new BookId(entity.getBookId())))
                                        .collect(toSet())));
    }

    /**
     * 映射读者预定信息
     * @author yangzk
     * @param patronDatabaseEntity 读者数据实体信息
     * @date 2024/1/16 16:05
     * @return
     **/
    default  Set<Tuple2<BookId, LibraryBranchId>> mapPatronHolds(PatronDatabaseEntity patronDatabaseEntity) {
        return patronDatabaseEntity
                .booksOnHold
                .stream()
                .map(entity -> Tuple.of((new BookId(entity.getBookId())), new LibraryBranchId(entity.getLibraryBranchId())))
                .collect(toSet());
    }
}
